package drds.datasource.connection_pool.impl;

/*import drds.common.$;
import drds.common.Constants;
import drds.common.ShouldNeverHappenException;*/

import drds.datasource.connection_pool.api.SqlException;
import drds.datasource.connection_pool.checker.ConnectionValidChecker;
import drds.datasource.connection_pool.checker.ConnectionValidCheckerImpl;
import drds.datasource.connection_pool.checker.ExceptionChecker;
import drds.datasource.connection_pool.checker.ExceptionCheckerImpl;
import drds.datasource.connection_pool.impl.holder.ConnectionHolder;
import drds.datasource.connection_pool.util.JdbcUtils;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import tool.plus.$;
import tool.plus.Constants;
import tool.plus.ShouldNeverHappenException;

import java.sql.*;
import java.util.Date;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;


@Slf4j
public abstract class AbstractDataSource implements drds.datasource.connection_pool.api.DataSource {
    public final static int DEFAULT_INITIAL_SIZE = 0;
    public final static int DEFAULT_MAX_ACTIVE_SIZE = 8;
    public final static int DEFAULT_MAX_IDLE = 8;
    public final static int DEFAULT_MIN_IDLE = 0;
    public final static int DEFAULT_MAX_WAIT = -1;
    public final static String DEFAULT_VALIDATION_QUERY = null; //
    //
    public final static boolean DEFAULT_TEST_ON_BORROW = false;
    public final static boolean DEFAULT_WHILE_IDLE = true;
    public final static boolean DEFAULT_TEST_ON_RETURN = false;
    //
    public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60 * 1000L;
    public static final long DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS = 500;
    public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3;
    public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L;
    public static final long DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 60L * 7;
    public static final long DEFAULT_PHY_TIMEOUT_MILLIS = -1;
    public final static Object PRESENT = new Object();
    final static AtomicLongFieldUpdater<AbstractDataSource> errorCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "errorCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> startTransactionCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "startTransactionCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> commitCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "commitCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> rollbackCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "rollbackCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> cachedPreparedStatementHitCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "cachedPreparedStatementHitCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> preparedStatementCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "preparedStatementCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> closedPreparedStatementCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "closedPreparedStatementCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> cachedPreparedStatementDeleteCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "cachedPreparedStatementDeleteCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> cachedPreparedStatementMissCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "cachedPreparedStatementMissCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> executeCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "executeCount");
    final static AtomicIntegerFieldUpdater<AbstractDataSource> createErrorCountUpdater = AtomicIntegerFieldUpdater
            .newUpdater(AbstractDataSource.class, "createErrorCount");
    final static AtomicIntegerFieldUpdater<AbstractDataSource> creatingCountUpdater = AtomicIntegerFieldUpdater
            .newUpdater(AbstractDataSource.class, "creatingCount");
    final static AtomicIntegerFieldUpdater<AbstractDataSource> directCreateCountUpdater = AtomicIntegerFieldUpdater
            .newUpdater(AbstractDataSource.class, "directCreateCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> createCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "createCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> destroyCountUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "destroyCount");
    final static AtomicLongFieldUpdater<AbstractDataSource> createStartNanosUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "createStartNanos");
    final static AtomicLongFieldUpdater<AbstractDataSource> failContinuousTimeMillisUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "failContinuousTimeMillis");
    final static AtomicIntegerFieldUpdater<AbstractDataSource> failContinuousUpdater = AtomicIntegerFieldUpdater
            .newUpdater(AbstractDataSource.class, "failContinuous");
    final static AtomicLongFieldUpdater<AbstractDataSource> connectionIdSeedUpdater = AtomicLongFieldUpdater
            .newUpdater(AbstractDataSource.class, "connectionIdSeed");
    public static int jdbc_url_type_mysql = -1;
    public static int jdbc_url_type_mariadb = 2;
    private static com.mysql.jdbc.Driver mysqlDriver = null;
    private static org.mariadb.jdbc.Driver mariadbDriver = null;

    static {
        try {
            Class.forName(com.mysql.jdbc.Driver.class.getName());
            Class.forName(org.mariadb.jdbc.Driver.class.getName());
            //
            com.mysql.jdbc.Driver mysqlDriver = new com.mysql.jdbc.Driver();
            org.mariadb.jdbc.Driver mariadbDriver = new org.mariadb.jdbc.Driver();
            //
            AbstractDataSource.mysqlDriver = mysqlDriver;
            AbstractDataSource.mariadbDriver = mariadbDriver;
        } catch (ClassNotFoundException e) {
            if (Constants.developMode) {
                e.printStackTrace();
            }
            String message = "缺少mysql/mariadb Driver";
            log.error(message, e);
            throw new RuntimeException(message);
        } catch (SQLException e) {

        }
    }

    //
    @Setter
    @Getter
    public final Map<PooledConnection, Object> activeConnections = new IdentityHashMap<PooledConnection, Object>();
    @Setter
    @Getter
    public volatile String jdbcUrl;
    @Setter
    @Getter
    public volatile String username;
    //
    @Setter
    @Getter
    public volatile String password;
    @Setter
    @Getter
    public volatile String defaultCatalog = null;
    @Setter
    @Getter
    public volatile Properties connectProperties = new Properties();
    @Setter
    @Getter
    public volatile int initialSize = DEFAULT_INITIAL_SIZE;
    @Setter
    @Getter
    public volatile int maxActive = DEFAULT_MAX_ACTIVE_SIZE;
    @Setter
    @Getter
    public volatile int minIdle = DEFAULT_MIN_IDLE;
    @Setter
    @Getter
    public volatile int maxIdle = DEFAULT_MAX_IDLE;
    @Setter
    @Getter
    public volatile long maxWait = DEFAULT_MAX_WAIT;
    @Setter
    @Getter
    public int notFullTimeoutRetryCount = 0;
    @Setter
    @Getter
    public volatile String validationQuery = DEFAULT_VALIDATION_QUERY;
    @Setter
    @Getter
    public volatile int validationQueryTimeout = -1;
    @Setter
    @Getter
    public volatile boolean testOnBorrow = DEFAULT_TEST_ON_BORROW;
    @Setter
    @Getter
    public volatile boolean testWhileIdle = DEFAULT_WHILE_IDLE;
    @Setter
    @Getter
    public volatile boolean testOnReturn = DEFAULT_TEST_ON_RETURN;
    @Setter
    @Getter
    public volatile boolean inited = false;
    @Setter
    @Getter
    public volatile boolean initExceptionThrow = true;
    @Setter
    @Getter
    public volatile ExceptionChecker exceptionChecker = new ExceptionCheckerImpl();
    @Setter
    @Getter
    public volatile int queryTimeout;
    @Setter
    @Getter
    public volatile int transactionQueryTimeout;
    @Setter
    @Getter
    public long createTimespan;
    @Setter
    @Getter
    public volatile int maxWaitThreadCount = -1;
    @Setter
    @Getter
    public volatile boolean accessToUnderlyingConnectionAllowed = true;
    @Setter
    @Getter
    public volatile long timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
    @Setter
    @Getter
    public volatile int numTestsPerEvictionRun = DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
    @Setter
    @Getter
    public volatile long minEvictableIdleTimeMillis = DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
    @Setter
    @Getter
    public volatile long maxEvictableIdleTimeMillis = DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS;
    @Setter
    @Getter
    public volatile long keepAliveBetweenTimeMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS * 2;
    @Setter
    @Getter
    public volatile long phyTimeoutMillis = DEFAULT_PHY_TIMEOUT_MILLIS;
    @Setter
    @Getter
    public volatile long maxUseCount = -1;
    @Setter
    @Getter
    public volatile boolean supportRemoveAbandoned;
    @Setter
    @Getter
    public volatile long removeAbandonedTimeoutMillis = 300 * 1000;
    @Setter
    @Getter
    public volatile boolean logAbandoned;
    @Setter
    @Getter
    public volatile List<String> connectionInitSqlList = new ArrayList<String>();
    @Setter
    @Getter
    public volatile long timeBetweenConnectErrorMillis = DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS;
    @Setter
    @Getter
    public volatile ConnectionValidChecker connectionValidChecker = new ConnectionValidCheckerImpl();
    @Setter
    @Getter
    public int connectionErrorRetryAttempts = 1;
    @Setter
    @Getter
    public boolean breakAfterAcquireFailure = false;
    @Setter
    @Getter
    public Date initedTime;
    @Setter
    @Getter
    public volatile long errorCount = 0L;
    @Setter
    @Getter
    public volatile long dupCloseCount = 0L;
    @Setter
    @Getter
    public volatile long startTransactionCount = 0L;
    @Setter
    @Getter
    public volatile long commitCount = 0L;
    @Setter
    @Getter
    public volatile long rollbackCount = 0L;
    @Setter
    @Getter
    public volatile long cachedPreparedStatementHitCount = 0L;
    @Setter
    @Getter
    public volatile long preparedStatementCount = 0L;
    @Setter
    @Getter
    public volatile long closedPreparedStatementCount = 0L;
    @Setter
    @Getter
    public volatile long cachedPreparedStatementCount = 0L;
    @Setter
    @Getter
    public volatile long cachedPreparedStatementDeleteCount = 0L;
    @Setter
    @Getter
    public volatile long cachedPreparedStatementMissCount = 0L;
    @Setter
    @Getter
    public volatile long executeCount = 0L;
    @Setter
    @Getter
    public volatile Throwable createError;
    @Setter
    @Getter
    public volatile Throwable lastError;
    @Setter
    @Getter
    public volatile long lastErrorTimeMillis;
    @Setter
    @Getter
    public volatile Throwable lastCreateError;
    @Setter
    @Getter
    public volatile long lastCreateErrorTimeMillis;
    @Setter
    @Getter
    public ReentrantLock lock;
    @Setter
    @Getter
    public Condition notEmpty;
    @Setter
    @Getter
    public Condition empty;
    @Setter
    @Getter
    public ReentrantLock activeConnectionLock = new ReentrantLock();
    @Setter
    @Getter
    public volatile int createErrorCount = 0;
    @Setter
    @Getter
    public volatile int creatingCount = 0;
    @Setter
    @Getter
    public volatile int directCreateCount = 0;
    @Setter
    @Getter
    public volatile long createCount = 0L;
    @Setter
    @Getter
    public volatile long destroyCount = 0L;
    @Setter
    @Getter
    public volatile long createStartNanos = 0L;
    @Setter
    @Getter
    public long timeBetweenLogStatsMillis;
    @Setter
    @Getter
    public int maxCreateTaskCount = 3;
    @Setter
    @Getter
    public boolean failFast = false;
    @Setter
    @Getter
    public volatile int failContinuous = 0;
    @Setter
    @Getter
    public volatile long failContinuousTimeMillis = 0L;
    @Setter
    @Getter
    public ScheduledExecutorService destroyScheduler;
    @Setter
    @Getter
    public ScheduledExecutorService createScheduler;
    @Setter
    @Getter
    public volatile boolean onFatalError = false;
    @Setter
    @Getter
    public volatile int onFatalErrorMaxActive = 0;
    @Setter
    @Getter
    public volatile long lastFatalErrorTimeMillis = 0;
    @Setter
    @Getter
    public volatile String lastFatalErrorSql = null;
    @Setter
    @Getter
    public volatile Throwable lastFatalError = null;
    @Setter
    @Getter
    public volatile long connectionIdSeed = 10000L;
    @Setter
    @Getter
    public volatile long statementIdSeed = 20000L;
    @Setter
    @Getter
    public volatile long resultSetIdSeed = 50000L;
    @Setter
    @Getter
    public volatile long transactionIdSeed = 60000L;
    @Setter
    @Getter
    public volatile long metaDataIdSeed = 80000L;
    @Setter
    @Getter
    private boolean clearFiltersEnable = true;
    @Setter
    @Getter
    private boolean dupCloseLogEnable = false;
    @Setter
    @Getter
    private Boolean useUnfairLock = null;
    @Setter
    @Getter
    private boolean useLocalSessionState = true;
    @Setter
    @Getter
    private boolean asyncCloseConnectionEnable = false;
    private int jdbcUrlType;


    public AbstractDataSource(boolean lockFair) {
        lock = new ReentrantLock(lockFair);

        notEmpty = lock.newCondition();
        empty = lock.newCondition();
    }

    public void setUseUnfairLock(boolean useUnfairLock) {
        if (lock.isFair() == !useUnfairLock) {
            return;
        }

        if (!this.inited) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                if (!this.inited) {
                    this.lock = new ReentrantLock(!useUnfairLock);
                    this.notEmpty = this.lock.newCondition();
                    this.empty = this.lock.newCondition();

                    this.useUnfairLock = useUnfairLock;
                }
            } finally {
                lock.unlock();
            }
        }
    }

    public Date getLastErrorTime() {
        if (lastErrorTimeMillis <= 0) {
            return null;
        }

        return new Date(lastErrorTimeMillis);
    }

    public Date getLastCreateErrorTime() {
        if (lastCreateErrorTimeMillis <= 0) {
            return null;
        }

        return new Date(lastCreateErrorTimeMillis);
    }

    public int getTransactionQueryTimeout() {
        if (transactionQueryTimeout <= 0) {
            return queryTimeout;
        }

        return transactionQueryTimeout;
    }

    public boolean isDupCloseLogEnable() {
        return dupCloseLogEnable;
    }

    public void incrementClosedPreparedStatementCount() {
        closedPreparedStatementCountUpdater.incrementAndGet(this);
    }

    public void incrementCommitCount() {
        commitCountUpdater.incrementAndGet(this);
    }

    public void incrementRollbackCount() {
        rollbackCountUpdater.incrementAndGet(this);
    }

    public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
        if (minEvictableIdleTimeMillis < 1000 * 30) {
            log.error("minEvictableIdleTimeMillis should be greater than 30000");
        }

        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public void setKeepAliveBetweenTimeMillis(long keepAliveBetweenTimeMillis) {
        if (keepAliveBetweenTimeMillis < 1000 * 30) {
            log.error("keepAliveBetweenTimeMillis should be greater than 30000");
        }

        this.keepAliveBetweenTimeMillis = keepAliveBetweenTimeMillis;
    }

    public void setMaxEvictableIdleTimeMillis(long maxEvictableIdleTimeMillis) {
        if (maxEvictableIdleTimeMillis < 1000 * 30) {
            log.error("maxEvictableIdleTimeMillis should be greater than 30000");
        }

        if (maxEvictableIdleTimeMillis < minEvictableIdleTimeMillis) {
            throw new IllegalArgumentException(
                    "maxEvictableIdleTimeMillis must be grater than minEvictableIdleTimeMillis");
        }

        this.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis;
    }

    public void setValidationQueryTimeout(int validationQueryTimeout) {
        if (validationQueryTimeout < 0) {
            log.error("validationQueryTimeout should be >= 0");
        }
        this.validationQueryTimeout = validationQueryTimeout;
    }

    public void setMaxWait(long maxWaitMillis) {
        if (maxWaitMillis == this.maxWait) {
            return;
        }

        if (maxWaitMillis > 0 && useUnfairLock == null && !this.inited) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                if ((!this.inited) && (!lock.isFair())) {
                    this.lock = new ReentrantLock(true);
                    this.notEmpty = this.lock.newCondition();
                    this.empty = this.lock.newCondition();
                }
            } finally {
                lock.unlock();
            }
        }

        if (inited) {
            log.error("maxWait changed : " + this.maxWait + " -> " + maxWaitMillis);
        }

        this.maxWait = maxWaitMillis;
    }

    public void setMinIdle(int value) {
        if (value == this.minIdle) {
            return;
        }

        if (inited && value > this.maxActive) {
            throw new IllegalArgumentException("minIdle greater than maxActive, " + maxActive + " < " + this.minIdle);
        }

        if (minIdle < 0) {
            throw new IllegalArgumentException("minIdle must > 0");
        }

        this.minIdle = value;
    }

    public void setInitialSize(int initialSize) {
        if (this.initialSize == initialSize) {
            return;
        }

        if (inited) {
            throw new UnsupportedOperationException();
        }

        this.initialSize = initialSize;
    }

    public void setUsername(String username) {
        if ($.equals(this.username, username)) {
            return;
        }

        if (inited) {
            throw new UnsupportedOperationException();
        }

        this.username = username;
    }

    public void setPassword(String password) {
        if ($.equals(this.password, password)) {
            return;
        }

        if (inited) {
            log.info("password changed");
        }

        this.password = password;
    }

    public abstract void setConnectProperties(Properties properties);

    public void setConnectionProperties(String connectionProperties) {
        if (connectionProperties == null || connectionProperties.trim().length() == 0) {
            setConnectProperties(null);
            return;
        }

        String[] entries = connectionProperties.split(";");
        Properties properties = new Properties();
        for (int i = 0; i < entries.length; i++) {
            String entry = entries[i];
            if (entry.length() > 0) {
                int index = entry.indexOf('=');
                if (index > 0) {
                    String name = entry.substring(0, index);
                    String value = entry.substring(index + 1);
                    properties.setProperty(name, value);
                } else {
                    // no value is empty string which is how java.util.Properties works
                    properties.setProperty(entry, "");
                }
            }
        }

        setConnectProperties(properties);
    }

    public void setUrl(String jdbcUrl) {
        if ($.equals(this.jdbcUrl, jdbcUrl)) {
            return;
        }

        if (inited) {
            throw new UnsupportedOperationException();
        }
        /**
         *  进行jdbcUrl鉴别
         */
        if (jdbcUrl.startsWith("jdbc:mysql://")) {
            jdbcUrlType = jdbc_url_type_mysql;
        } else if (jdbcUrl.startsWith("jdbc:mariadb://")) {
            jdbcUrlType = jdbc_url_type_mariadb;
        } else {
            throw new IllegalArgumentException("不支持当前类型的jdbcUrlType");
        }

        this.jdbcUrl = jdbcUrl;

    }


    public void validateConnection(@NonNull Connection connection) throws SQLException {
        if (connection.isClosed()) {
            throw new SQLException("validateConnection: connection closed");
        }
        String validationQuery = getValidationQuery();
        if (connectionValidChecker != null) {
            boolean valid = true;
            Exception error = null;
            try {
                valid = connectionValidChecker.isValid(connection, this.validationQuery, validationQueryTimeout);
            } catch (SQLException ex) {
                throw ex;
            } catch (Exception ex) {
                error = ex;
            }

            if (!valid) {
                SQLException sqlError = error != null ? //
                        new SQLException("validateConnection false", error) //
                        : new SQLException("validateConnection false");
                throw sqlError;
            }
            return;
        }

        if (null != validationQuery) {
            Statement statement = null;
            ResultSet resultSet = null;
            try {
                statement = connection.createStatement();
                if (getValidationQueryTimeout() > 0) {
                    statement.setQueryTimeout(getValidationQueryTimeout());
                }
                resultSet = statement.executeQuery(validationQuery);
                if (!resultSet.next()) {
                    throw new SQLException("validationQuery didn't return a row");
                }
            } finally {
                JdbcUtils.close(resultSet);
                JdbcUtils.close(statement);
            }
        }
    }


    public boolean testConnectionInternal(ConnectionHolder connectionHolder, Connection connection) {
        boolean valid = false;
        try {
            valid = connectionValidChecker.isValid(connection, validationQuery, validationQueryTimeout);
            if (valid) {
                long currentTimeMillis = System.currentTimeMillis();
                if (connectionHolder != null) {
                    connectionHolder.lastValidTimeMillis = currentTimeMillis;
                }
            }
            return valid;
        } catch (SQLException e) {
            return false;
        }


    }


    public void setClearFiltersEnable(boolean clearFiltersEnable) {
        this.clearFiltersEnable = clearFiltersEnable;
    }

    public long createConnectionId() {
        return connectionIdSeedUpdater.incrementAndGet(this);
    }


    void initStatement(PooledConnection pooledConnection, Statement statement) throws SqlException {
        boolean underlyingAutoCommit = !pooledConnection.getConnectionHolder().underlyingAutoCommit;

        int queryTimeout = underlyingAutoCommit ? getTransactionQueryTimeout() : getQueryTimeout();

        if (queryTimeout > 0) {
            try {
                statement.setQueryTimeout(queryTimeout);
            } catch (SQLException e) {
                e.printStackTrace();
                throw new SqlException(e);
            }
        }
    }


    public abstract void handleConnectionException(PooledConnection pooledConnection, SQLException t, String sql)
            throws SqlException;

    public abstract void recycle(PooledConnection pooledConnection) throws SqlException;

    public Connection createConnection(String url, Properties info) throws SqlException {
        Connection connection = null;
        //connection=DriverManager.getConnection(url, info);
        try {
            if (jdbcUrlType == jdbc_url_type_mysql) {
                connection = mysqlDriver.connect(url, info);
            } else if (jdbcUrlType == jdbc_url_type_mariadb) {
                connection = mariadbDriver.connect(url, info);
            } else {
                throw new ShouldNeverHappenException();
            }
        } catch (SQLException e) {
            JdbcUtils.log(log, "jdbcDriver.connect(url, info)", e);
            return null;
        }
        createCountUpdater.incrementAndGet(this);
        return connection;
    }

    public Connection createConnection() throws SqlException {
        String url = getJdbcUrl();
        //
        String user = getUsername();
        String password = getPassword();
        Properties properties = new Properties();
        properties.put("user", user);
        properties.put("password", password);
        Properties connectProperties = getConnectProperties();
        if (connectProperties != null) {
            properties.putAll(connectProperties);
        }


        long connectStartNanos = System.nanoTime();


        createStartNanosUpdater.set(this, connectStartNanos);
        creatingCountUpdater.incrementAndGet(this);
        Connection connection = null;
        try {
            connection = createConnection(url, properties);
            if (connection == null) {
                throw new SQLException("connect error, url " + url);
            }
            initConnection(connection);
            validateConnection(connection);
            setFailContinuous(false);
            setCreateError(null);
        } catch (SQLException ex) {
            setCreateError(ex);
            JdbcUtils.close(connection);
            throw new SqlException(ex);
        } catch (RuntimeException ex) {
            setCreateError(ex);
            JdbcUtils.close(connection);
            throw ex;
        } catch (Error ex) {
            createErrorCountUpdater.incrementAndGet(this);
            setCreateError(ex);
            JdbcUtils.close(connection);
            throw ex;
        } finally {
            long nano = System.nanoTime() - connectStartNanos;
            createTimespan += nano;
            creatingCountUpdater.decrementAndGet(this);
        }

        return connection;
    }

    public void setCreateError(Throwable ex) {
        if (ex == null) {
            lock.lock();
            try {
                if (createError != null) {
                    createError = null;
                }
            } finally {
                lock.unlock();
            }
            return;
        }

        createErrorCountUpdater.incrementAndGet(this);
        long now = System.currentTimeMillis();
        lock.lock();
        try {
            createError = ex;
            lastCreateError = ex;
            lastCreateErrorTimeMillis = now;
        } finally {
            lock.unlock();
        }
    }

    public boolean isFailContinuous() {
        return failContinuousUpdater.get(this) == 1;
    }

    public void setFailContinuous(boolean fail) {
        if (fail) {
            failContinuousTimeMillisUpdater.set(this, System.currentTimeMillis());
        } else {
            failContinuousTimeMillisUpdater.set(this, 0L);
        }

        boolean currentState = failContinuousUpdater.get(this) == 1;
        if (currentState == fail) {
            return;
        }

        if (fail) {
            failContinuousUpdater.set(this, 1);
            if (log.isInfoEnabled()) {
                log.info("dataSource failContinuous is true");
            }
        } else {
            failContinuousUpdater.set(this, 0);
            if (log.isInfoEnabled()) {
                log.info("dataSource failContinuous is false");
            }
        }
    }


    public void initConnection(Connection connection) throws SQLException {
        if (!connection.getAutoCommit()) {
            connection.setAutoCommit(true);
        }
        //
        if (getDefaultCatalog() != null) {
            connection.setCatalog(getDefaultCatalog());
        }
        Collection<String> initSqls = getConnectionInitSqlList();
        if (initSqls.size() == 0) {
            return;
        }

        Statement statement = null;
        try {
            statement = connection.createStatement();
            for (String sql : initSqls) {
                statement.execute(sql);
            }
        } finally {
            JdbcUtils.close(statement);
        }
    }

    public abstract int getActivePeak();


    public abstract String getProperties();


    public void closePreapredStatement(PreparedStatement preparedStatement) throws SQLException {
        if (preparedStatement == null) {
            return;
        }
        closedPreparedStatementCountUpdater.incrementAndGet(this);


        JdbcUtils.close(preparedStatement);
    }


    public abstract void discardConnection(Connection realConnection);

    public boolean isAsyncCloseConnectionEnable() {
        if (isSupportRemoveAbandoned()) {
            return true;
        }
        return asyncCloseConnectionEnable;
    }


    public void setCreateScheduler(ScheduledExecutorService createScheduler) {
        if (isInited()) {
            throw new RuntimeException("dataSource inited.");
        }
        this.createScheduler = createScheduler;
    }


    public void setDestroyScheduler(ScheduledExecutorService destroyScheduler) {
        if (isInited()) {
            throw new RuntimeException("dataSource inited.");
        }
        this.destroyScheduler = destroyScheduler;
    }

    public boolean isInited() {
        return this.inited;
    }


    public void setMaxCreateTaskCount(int maxCreateTaskCount) {
        if (maxCreateTaskCount < 1) {
            throw new IllegalArgumentException();
        }

        this.maxCreateTaskCount = maxCreateTaskCount;
    }


}
